home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Technotools
/
Technotools (Chestnut CD-ROM)(1993).ISO
/
lang_c
/
ldb
/
ldb.doc
< prev
next >
Wrap
Text File
|
1991-10-21
|
19KB
|
372 lines
Dear C++ Programmer,
Thank you for downloading the Loose Data Binder (LDB). The LDB is
a generic persistent container class that is less filling (smaller
code sizes and faster execution) and less stuffy (no towering
convoluted hierarchies) than other conventional container class
libraries found packaged with C++ compilers and application
framework tools.
The LDB is offered to you as shareware, meaning try before you buy.
For other than evaluation purposes directed at reaching a buy or
no buy decision, you are required by law to register the LDB. A
hard copy manual will be sent to users upon receiving registration.
The source code is also provided on either 3.5" or 5 1/4" DOS
diskette (please specify). The source is broken down into many
files along with a makefile for library building. PSW reserves
the right to withdraw this offer at any time.
LDB v1.4 $30 in U.S. $40 elsewhere
Please make checks payable to:
PSW / Power SoftWare
P.O. Box 10072
McLean, VA 22102 8072 USA
If you have questions about the LDB you can contact me at:
John Small
Voice: (703) 759-3838
CIS: 73757,2233
Exec PC: MSMALL
I have included tutorial chapter from the manual to get you
started along with seven demos.
Chapter 3
Tutorial
Copy binder.hpp, sbinder.hpp, sdata.hpp, and cbinder.hpp to your
compiler's standard header directory and the *.cpp files to your
source code directory if you have not already done so. Refer to
the header file listings in the appendix for a quick reference to
the various member functions of the LDB. If a member function's
operation is unclear, look up its entry in the reference chapter
for an explanation. Be sure to study the code of these examples,
then compile (don't forget to link to binder.obj, sbinder.obj,
sdata.obj and/or cbinder.obj as appropriate) and run them to see
the results. Are the results what you expected? Okay let's begin.
BDRDEM1.CPP
This first example is a demo of the two Binder class constructors
used in various configurations. Find their declarations in
binder.hpp or the reference chapter and make a mental note of their
parameters and defaults! I will only highlight some of their uses
here.
Okay let's walk through the code of this first example which can of
course be found in bdrdem1.cpp. Starting in main() the first
Binder, B1, is defined and constructed with all default parameters.
As you can see from binder.hpp, the binder is constructed with
flags = BDR_NO_FREE, maxNodes = BDR_MAXNODES, limit = BDR_LIMIT,
and delta = BDR_DELTA. Don't worry about this stuff for now. All
we have here is a very basic binder behaving as an elastic array of
void pointers which you supply.
(see bdrdem1.cpp) omitted here to reduce download size
The for loop inserts the char pointers of the vector V into the
rear of the binder treating it as if it were a queue. The loop is
terminated when the NULL pointer of V is attempted to be inserted
into the binder queue. The insQ() primitive always returns the
pointer it inserts into the binder and of course it's not going to
ever insert the NULL pointer so voiD0, the NULL void pointer is
returned to indicate failure!
Every binder can be treated as a list by internalizing the concept
of some node being the current node. When a binder is first
created no node is current. The stack, queue, deque, and array
primitives don't affect the current node index, after all it is a
list concept! Instead the primitives ins(), del(), insSort(),
next(), and prev(), etc. are the list primitives and they do affect
the current node pointer. The next() primitive advances the
current index and returns the void pointer to that new current node
if there is one. I'm treating the returned pointer from next() in
the example as a boolean value to test when I reach the end of the
list. One of the beauties (and pains) of C++ is that of overloaded
operators. The implicit type cast operator voiD simply returns the
pointer to the current node. You will soon notice that I have a
habit of capitalizing the last letter of type names to indicate a
pointer to that type. Here voiD means a pointer to void.
Okay, enough hand holding, it's time to speed up! The next binder,
B2, is constructed so that it is limited to a maximum of 3 nodes.
Upon destruction of the binder all nodes will be deleted which is
indicated by setting the flags parameter to BDR_OK_FREE. The
default parameter is BDR_NO_FREE which mean that nodes are simply
released when the binder is destructed. Furthermore the functions
that attempt to delete nodes are inhibited, e.g. atFree().
Notice the forEach() iterator and how it applies the block
parameter to each element in the binder in sequential order. Block
is SmallTalk terminology for a function passed as a parameter
(C/C++ programmers say function pointers).
The last binder of this example, B3, demonstrates the other
constructor which takes a vector (array) of pointers and explodes
them into a binder. By explode, I mean each cell of the vector
becomes a node in the binder. Of course the original vector is
left intact. You can implode a binder back into a dynamically
allocated vector which is returned by the vector() primitive. The
overloaded ++ operator simply applies the next() primitive to the
binder. Find the definition of operator++() in binder.hpp.
BDRDEM2.CPP
In this second example, you will see several of the various stack,
queue, list, array, and sort primitives in action. This is only a
sampling, see the class declaration of Binder in binder.hpp for a
complete listing of primitives. If you grasp the basic structure
and operation of the binder here you can fill in the details from
reading the header file.
(see bdrdem2.cpp) omitted here to reduce download size
With the first binder, B, of this example we see the primitive
atIns() used. A binder's cells are indexed from 0 to n - 1 just
like a conventional C array of n cells. AtIns() is not a list
primitive, per say, and thus doesn't affect the binder's current
node index. When no node is current, the current node number is
internally set to "nodes", one position pass the last element in
the binder. Primitives that search for a node returning its index
will return BDR_NOTFOUND to indicate no node found. What may seem
strange to you at first is that BDR_NOTFOUND is a large positive
integer, the largest number of nodes that can ever be stored in a
binder in fact. Remember that one less than this number will be
the last element's index in a completely filled binder.
The second binder, B2, is filled with a sorted list of the nodes
from binder B. Now both B and B2 contain pointers to the same
nodes! This could be a potentially hazardous situation but since
both binders don't delete their nodes upon destruction (flags =
BDR_NO_FREE in constructor) nothing is accidently deleted twice
or even once since nothing was allocated to begin with.
SBDRDEM1.CPP
SBinder, derived from Binder and Streamable, is streamable or
persistent in OOP's parlance. In other words, an SBinder can be
saved on a stream and reloaded later. Streamable nodes have
provisions for recording double ownerships and automatically
safeguarding against accidental double deletions at the same time
as providing streamability as we will see in this example.
A class must include the STREAMABLE macro in its public section and
be derived either publicly or privately from Streamable but never
virtually or otherwise multiply inherited in order for it to be
"streamable" (see StreamableInt below). If Streamable were allowed
to be a virtual base class its pointer couldn't be type casted to
its derived classes when reloading. All that is known at reload
time is that it's Streamable and you get back a Streamable class
instance pointer. The ID() member function must then be called to
find out what type of class was reloaded. On the other hand if
Streamable were allowed to be multiply inherited the overloaded
stream operators couldn't decide, when storing on a stream, in
which stream base is the valid id. You can elect not to use the
STREAMABLE macro once you understand what is required of a class in
a streamable hierarchy. I didn't use the STREAMABLE macro in
declaring SBinder or CBinder but I did for SData.
Be sure you remember to call ClassName::registerClass() before
attempting to store a instance of a class onto a stream! The
STREAMABLE macro declares two functions: store() and load() which
you must define for your derived class.
To run this example and see the results, redirect its output to a
file and than view it. I've turned on debugging and the output
will zip by on the screen otherwise.
(see sbdrdem1.cpp) omitted here to reduce download size
When an object X is referenced by more than one other objects that
are being stored onto a stream, the first object that attempts to
store X will succeed while the rest will automatically store only
a link to X. When reloading X, a link to X will be automatically
held in a holding pen inside of the StreamableClassRegistry if
multiple attempts had been made storing X. When the additional
objects referencing X are reloaded, the links to X are recovered.
When the last reference to X is loaded and the link is
reconstructed then X is released automatically from the holding
pen. This mechanism prevents multiple copies of X from being
stored on the stream and thus multiple copies reloaded. Before
attempting to store a group of objects again, perhaps to another
stream, you must call restream() for each object to reset this
automatic linking mechanism (the SBinder automatically calls each
node's restream()). To clear the holding pen between loads from
the same stream or even different streams, you must call
RestreamRegistry()!
A lot of important points are made in this example, so study it
carefully! Notice that function pointers can also be streamed when
registered with the StreamableFncPtrRegistry! And of course the
ID's for the classes you derive must be unique. Please note that
1, 2, and 3 are reserved for the SBinder, SData, and CBinder
classes respectively.
When studying this example's output notice that the binder's
internal compare function pointer is stored on the stream and
recovered and used to sort the reloaded binder. You can trace the
multiple linking of the streamable integers. When the binders are
being destructed, the nodes are unlinked and only deleted when the
refCount is zero.
SBDRDEM2.CPP
An example of streamable strings can be found in sbdrdem2.cpp.
Since sbdrdem2.cpp is so much like sbdrdem1.cpp except for handling
strings instead of integers, its listing has be omitted here. You
should take a look at it, however, to reenforce you understanding
of deriving a class from Streamable for variant data.
SDDEM1.CPP
As you can see it takes a bit of work to make an item streamable so
for non class objects, the SData class has been provided for your
convenience. The next example shows how to use the SData class to
wrap data at run time in a truly streamable package, complete with
automatic protection against double deletion saving you the effort
of deriving your own classes from Streamable. SData allows fully
heterogeneous data to be bound together in the same binder.
Notice in sddem1.cpp, that both integers and strings are bound
together in the same binder! The display and sort compare
functions are coded to act on both integers and strings! A more
elegant approach would be to derive a class from SData that knows
how to printOn() a stream and/or showIt() self on the console and
perform some type of compare function.
(see sddem1.cpp) omitted here to reduce download size
The binders in sddem1.cpp are able to bind truly heterogeneous data
and stream it! The test of multilinking is also a test of
protecting against double deletion. The string "list programming!"
isn't deleted twice in either the original binder or the binder
loaded from the stream.
CBINDER.CPP
If the data you want bound in a streamable binder is homogeneous
then the CBinder can improve efficiency over using SData with the
SBinder. The CBinder can automatically handle fixed sized data
such as structures and homogeneous variant data such as C strings
which it defaults to, providing for both fixed and variant data the
necessary packaging for streamability. Data can be cloned and
copied on the fly as well but all nodes MUST be dynamically
allocated! Keep in mind that CBinder nodes don't have the double
ownership protection built-in that a true class derived from
Streamable has but it doesn't require the associated coding effort
either! It's also possible to derive new classes from CBinder for
handling additional sorts of homogeneous variant data by overriding
the Dstore(), Dload(), Dfree(), Dclone() and Dcopy() virtual
functions.
By the way, CBinder is derived from the SBinder which in turn was
derived from Binder and Streamable. Studying the cbinder.cpp
source code is another excellent way to learn about building your
own streamable classes. If you do, you'll notice that
CBinder::store() only has to worry about storing the additional
fields of the CBinder, i.e. sizeofData, and relies on a call to
SBinder::store() to store the SBinder's fields. Likewise
CBinder::load() calls SBinder::load() to load the SBinder's fields.
Writing your own load function is the more difficult of the two.
Your load() should be written in such a way as to allow it to be
called from a derived class' load(), hence the test of the InstancE
pointer and the subsequent call of the constructor if it's NULL
(see CBinder::load() in cbinder.cpp). You may be wondering where
this constructor was declared; in the STREAMABLE macro of course
(see sbinder.hpp)! It has a dummy first parameter with no default
to insure that it is a unique, unambiguous constructor from any you
might declare for your newly derived class. Notice too that
initialization is left to a construct() function which can be
called by either a regular constructor or
YourStreamableClass::load() to initialize only the fields the
load() is responsible for. The load function has to be a static
member function since it is not permissible in C++ to take the
address of a constructor and a function pointer had to be recorded
in the StreamableClassRegistry which could be call to reconstruct
an instance when reloading. Hence the load() of the class being
loaded calls the default constructor defined in the STREAMABLE
macro (how else can virtual function tables and the like be
initialized in a port way?). The construct() is called to
initialize only the fields of the derived class while loading the
rest of the base class members is accomplished by calling the base
class' load(). You may call the base's load() either before or
after extracting data from the stream but obviously your store()
function must have likewise stored the data in the same fashion.
CBDRDEM1.CPP
And now for a CBinder demo of fixed sized nodes. Remember a
CBinder inherits all the member functions of the SBinder and that
nodes must be dynamically allocated since they are deleted when the
CBinder is destructed (CBinder constructor calls SBinder
constructor with flags set to BDR_OK_FREE). Since the cloned nodes
of the CBinder aren't instances of a class derived from Streamable,
automatic protection against double deletion of doubly owned nodes
can't be provided as is the case with nodes derived from
Streamable. Moral: don't put statically allocated data into a
CBinder via the inherited SBinder member functions. Likewise don't
put the same node into a CBinder more than once via the SBinder
functions. If you do, then when the CBinder goes to destruct
itself or if you call allFree() or atFree(), etc. it's possible to
either attempt to delete a statically allocated node or some node
that has already been deleted - look out (crash)! You might wonder
why the SBinder functions are left exposed (publicly inherited) -
because you algorithm may require must nodes between CBinder such
as with queuing network problems or other simulations.
You'll note in the example that new primitives have be added by the
CBinder class, e.g. pushCLN(), which do the same thing that their
SBinder counterparts do only cloning or copying the data as
required automatically but otherwise performing the same operation.
You could do the same thing yourself without a CBinder but you
would have to clone and/or copy everything yourself. If you are
porting from FlexList, you'll see that the CopyBinder rounds out
the SBinder to encompass the full "by value" operational capability
of a FlexList.
This demo shows that the CBinder provides for synthetic streamable
nodes without your having to derive a new class from Streamable.
You must still register the CBinder with the stream registry, and
function pointers as usual, as well as calling restream() but you
are saved from rewriting store() and load() functions for a
packaging class derived from streamable.
(see cbdrdem1.cpp) omitted here to reduce download size
CBDRDEM2.CPP
Be sure to look at cbdrdem2.cpp to see how the CBinder defaults to
handling strings so you never have to write something like
sbdrdem2.cpp yourself.
SUMMARY
As you become more familiar with the LDB family you will realize
that you can derive your own LDB classes from Binder, SBinder,
SData, and CBinder. For example, SBinder along with SData can be
use as a basis for an adjacency list binder, or sparse matrix,
tree, or graph network because of the strategic virtualizing of
member functions such as Dfree(), Dstore(), Dload() and the back-
link capability of the Streamable class. Note also that you can
derive LDB's from the CBinder to handle what would otherwise be
non-streamable classes and structures thanks to the virtualization
of such functions as Dclone() and Dcopy() and Dfree(). The
possibilities are enormous.